﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace Sttplay.Net.UDP
{
    public class TurnClient
    {
        private const uint MSG_LOGIN = 1280264009;
        private const uint MSG_LOGOUT = 1280264015;
        private const uint MSG_HEARTBEAT = 1212498258;
        private const uint MSG_TURN = 1414877774;
        private const uint MSG_CLIENTS = 1129072979;
        private const uint MAX_TIME_OUT = 10000;
        private RUDPEx rudp;

        public event Action<bool> onConnectCompleted;
        public event Action onDisconnected;
        public event Action<byte[]> onRecv;
        public event Action<uint[]> onGetClients;
        private bool isConnected = false;
        private bool isConnecting = false;
        private uint connectTs = 0;
        private uint beginConnectTs = 0;
        private uint connectTimeout;
        private uint connectSuccessTs;
        private uint lastHBTs;

        private EndPoint targetAddr;
        private uint selfID, targetID;
        private ulong selfLID, targetLID;
        private ulong selfLHID, getClientLHID;

        public TurnClient(string ip, uint port, uint client)
        {
            targetAddr = new IPEndPoint(IPAddress.Parse(ip), (int)port);
            selfID = client;
            rudp = new RUDPEx();
            if (!rudp.Initialize(IPAddress.Any, 0))
                throw new Exception("udp initialize failed");
   
            rudp.onRecv += InternalRecv;
            rudp.onSend += InternalSend;
        }



        public void Connect(uint client, uint timeout)
        {
            Disconnect();
            isConnecting = true;
            connectTimeout = timeout;
            targetID = client;
            ulong temp = MSG_TURN;
            targetLID = temp << 32 | targetID;
            beginConnectTs = rudp.GetTimestamp();
            rudp.SetProtocolFrame(targetLID);
        }

        public void Disconnect()
        {
            if (isConnected)
            {
                ulong temp = MSG_LOGOUT;
                ulong ubuff = temp << 32 | selfID;
                byte[] buff = BitConverter.GetBytes(ubuff);
                rudp.SendTo(buff, 0, buff.Length, targetAddr, false);
            }
            isConnecting = false;
            isConnected = false;
            connectTs = 0;
        }

        public RUDP GetUDP()
        {
            return rudp;
        }

        public void Update(uint timeout)
        {
            if (isConnecting)
            {
                if (rudp.GetTimestamp() - connectTs > 100)
                {
                    connectTs = rudp.GetTimestamp();
                    ulong temp = MSG_LOGIN;
                    selfLID = temp << 32 | selfID;
                    byte[] buff = BitConverter.GetBytes(selfLID);
                    rudp.SendTo(buff, 0, buff.Length, targetAddr, false);
                }
                if (rudp.GetTimestamp() - beginConnectTs > connectTimeout)
                {
                    isConnecting = false;
                    if (onConnectCompleted != null)
                        onConnectCompleted(false);
                }
            }
            if (isConnected && rudp.GetTimestamp() - connectSuccessTs > 1000)
            {
                connectSuccessTs = rudp.GetTimestamp();
                ulong temp = MSG_HEARTBEAT;
                selfLHID = temp << 32 | selfID;
                byte[] buff = BitConverter.GetBytes(selfLHID);
                uint dt = rudp.GetTimestamp() - lastHBTs;
                int sendCount = dt > (MAX_TIME_OUT / 2) ? 2 : 1;
                for (int i = 0; i < sendCount; i++)
                    rudp.SendTo(buff, 0, buff.Length, targetAddr, false);
                if (dt > MAX_TIME_OUT)
                    ExceptionDisconnect();
            }
            rudp.Update((int)timeout);
        }

        public void SendTo(byte[] data, int index, int size)
        {
            if (!isConnected) return;
            rudp.SendTo(data, index, size, targetAddr, true);
        }

        public void GetClients()
        {
            if (!isConnected) return;
            ulong temp = MSG_CLIENTS;
            getClientLHID = temp << 32 | selfID;
            byte[] buff = BitConverter.GetBytes(getClientLHID);
            rudp.SendTo(buff, 0, buff.Length, targetAddr, false);
        }

        private void InternalRecv(byte[] buffer, bool reliable, EndPoint endPoint)
        {
            if (reliable)
            {
                if (onRecv!= null)
                    onRecv(buffer);
            }
            else
            {
                if (buffer.Length == sizeof(ulong))
                {
                    ulong msg = BitConverter.ToUInt64(buffer, 0);
                    if (msg == selfLID + 1)
                    {
                        isConnecting = false;
                        isConnected = true;
                        connectSuccessTs = rudp.GetTimestamp();
                        if (onConnectCompleted != null)
                            onConnectCompleted(true);
                        lastHBTs = rudp.GetTimestamp();
                    }
                    else if (msg == selfLHID + 1)
                    {
                        lastHBTs = rudp.GetTimestamp();
                    }
                }
                else if (buffer.Length > sizeof(ulong))
                {
                    ulong msg = BitConverter.ToUInt64(buffer, 0);
                    if (msg == getClientLHID + 1)
                    {
                        if (onGetClients!= null)
                        {
                            List<uint> v = new List<uint>();
                            for (int i = 0; i < (buffer.Length - sizeof(ulong)) / sizeof(uint); i++)
                                v.Add(BitConverter.ToUInt32(buffer, sizeof(ulong) + sizeof(uint) * i));
                            onGetClients(v.ToArray());
                        }
                    }
                }
            }
        }

        private void InternalSend(RCTP.Message message, RCTP.SendState state)
        {
            if (message.state == RCTP.PackageState.PSH && state == RCTP.SendState.MaxTimeout && isConnected && onDisconnected != null)
                ExceptionDisconnect();
            if (state == RCTP.SendState.Success)
                lastHBTs = rudp.GetTimestamp();
        }
        private void ExceptionDisconnect()
        {
            isConnected = false;
            Disconnect();
            rudp.ClearCache();
            if (onDisconnected != null)
                onDisconnected();
        }


    }
}
